package org.greatlogic.gwtfilereaderwidget.client.itunes;

import com.google.gwt.regexp.shared.MatchResult;
import com.google.gwt.regexp.shared.RegExp;
import org.greatlogic.gwtfilereaderwidget.client.itunes.ITunesEnums.EConjunction;
import org.greatlogic.gwtfilereaderwidget.client.itunes.ITunesEnums.ETrackKeyDataType;
import org.greatlogic.gwtfilereaderwidget.client.itunes.ITunesEnums.ETrackKeyType;
import org.greatlogic.gwtfilereaderwidget.client.itunes.ITunesEnums.EOperator;

class ConditionLeafNode implements IConditionNode {
//--------------------------------------------------------------------------------------------------
private EConjunction        _conjunction;
private ETrackKeyType            _keyType;
private long[]              _operandsLong;
private String[]            _operandsString;
private String[]            _operandsStringLower;
private EOperator           _operator;
private ConditionParentNode _parentNode;
private RegExp[]            _regExps;            /* used for "like", "in like", etc. */
//--------------------------------------------------------------------------------------------------
private ConditionLeafNode(final EConjunction conjunction, final ETrackKeyType keyType,
                          final EOperator operator, final int numberOfOperands)
  throws ITunesException {
  _conjunction = conjunction;
  _keyType = keyType;
  _operator = operator;
  _operator.validateOperands(numberOfOperands);
} // ConditionLeafNode()
//--------------------------------------------------------------------------------------------------
ConditionLeafNode(final EConjunction conjunction, final ETrackKeyType keyType, final EOperator operator)
  throws ITunesException {
  this(conjunction, keyType, operator, 0);
} // ConditionLeafNode()
//--------------------------------------------------------------------------------------------------
ConditionLeafNode(final EConjunction conjunction, final ETrackKeyType keyType, final EOperator operator,
                  final long... operands) throws ITunesException {
  this(conjunction, keyType, operator, operands == null ? 0 : operands.length);
  _operandsLong = operands;
} // ConditionLeafNode
//--------------------------------------------------------------------------------------------------
ConditionLeafNode(final EConjunction conjunction, final ETrackKeyType keyType, final EOperator operator,
                  final String... operands) throws ITunesException {
  this(conjunction, keyType, operator, operands == null ? 0 : operands.length);
  _operandsString = operands;
  if (_operandsString != null) {
    _operandsStringLower = new String[_operandsString.length];
    for (int operandIndex = 0; operandIndex < _operandsString.length; ++operandIndex) {
      _operandsStringLower[operandIndex] = _operandsString[operandIndex].toLowerCase();
    }
  }
  if (operator.getUsesRegex() && _operandsString != null) {
    _regExps = new RegExp[_operandsString.length];
    for (int operandIndex = 0; operandIndex < _operandsString.length; ++operandIndex) {
      _regExps[operandIndex] = RegExp.compile(_operandsString[operandIndex]);
    }
  }
} // ConditionLeafNode()
//--------------------------------------------------------------------------------------------------
/**
* If the operands have been supplied as numeric values but the comparison requires strings then
* convert each of the long operands to a string.
*/
private void convertLongsToStrings() {
  if (_operandsString == null) {
    _operandsStringLower = new String[_operandsLong.length];
    for (int operandIndex = 0; operandIndex < _operandsLong.length; ++operandIndex) {
      _operandsStringLower[operandIndex] = String.valueOf(_operandsLong[operandIndex]);
    }
  }
} // convertLongsToStrings()
//--------------------------------------------------------------------------------------------------
/**
* If the operands have been supplied as strings but the comparison requires numeric values then
* convert each of the long operands to a string.
*/
private void convertStringsToLongs() {
  if (_operandsLong == null) {
    _operandsLong = new long[_operandsString.length];
    for (int operandIndex = 0; operandIndex < _operandsString.length; ++operandIndex) {
      _operandsLong[operandIndex] = ITunesUtil.stringToLong(_operandsString[operandIndex], 0);
    }
  }
} // convertLongsToStrings()
//--------------------------------------------------------------------------------------------------
@Override
public boolean evaluate(final Track track) throws ITunesException {
  boolean result = true;
  KeyAndValueEntry keyAndValueEntry = track.getKeyAndValueEntry(_keyType);
  if (_keyType.getKeyDataType() == ETrackKeyDataType.Boolean) {
    result = keyAndValueEntry.getValueAsBoolean() ? _operator == EOperator.True
                                                 : _operator == EOperator.False;
  }
  else if (_keyType.getKeyDataType() == ETrackKeyDataType.Integer) {
    convertStringsToLongs();
    long value = keyAndValueEntry.getValueAsLong();
    switch (_operator) {
      case Equals:
        result = value == _operandsLong[0];
        break;
      case GreaterThan:
        result = value > _operandsLong[0];
        break;
      case GreaterThanOrEqual:
        result = value >= _operandsLong[0];
        break;
      case In:
        result = false;
        for (long operand : _operandsLong) {
          result = operand == value;
          if (result) {
            break;
          }
        }
        break;
      case LessThan:
        result = value < _operandsLong[0];
        break;
      case LessThanOrEqual:
        result = value <= _operandsLong[0];
        break;
      case NotEquals:
        result = value != _operandsLong[0];
        break;
      case NotIn:
        for (long operand : _operandsLong) {
          result = operand != value;
          if (!result) {
            break;
          }
        }
        break;
      default:
        throw new ITunesException("Unhandled Operator in condition:" + toString());
    }
  }
  else { // track value type == ETrackKeyDataType.Date or String
    convertLongsToStrings();
    String value = keyAndValueEntry.getValueAsString();
    if (_operator.getUsesRegex()) {
      MatchResult matchResult = _regExps[0].exec(value);
      result = _operator == EOperator.Matches ? matchResult != null : matchResult == null;
    }
    else {
      String valueLower = value.toLowerCase();
      int compareResult = valueLower.compareTo(_operandsStringLower[0]);
      switch (_operator) {
        case Equals:
          result = compareResult == 0;
          break;
        case GreaterThan:
          result = compareResult > 0;
          break;
        case GreaterThanOrEqual:
          result = compareResult >= 0;
          break;
        case In:
          result = false;
          for (String operand : _operandsStringLower) {
            result = valueLower.equals(operand);
            if (result) {
              break;
            }
          }
          break;
        case LessThan:
          result = compareResult < 0;
          break;
        case LessThanOrEqual:
          result = compareResult <= 0;
          break;
        case NotEquals:
          result = compareResult != 0;
          break;
        case NotIn:
          for (String operand : _operandsStringLower) {
            result = !valueLower.equals(operand);
            if (!result) {
              break;
            }
          }
          break;
        default:
          throw new ITunesException("Unhandled Operator in condition:" + toString());
      }
    }
  }
  return result;
} // evaluate()
//--------------------------------------------------------------------------------------------------
@Override
public EConjunction getConjunction() {
  return _conjunction;
} // getConjunction()
//--------------------------------------------------------------------------------------------------
@Override
public ConditionParentNode getParentNode() {
  return _parentNode;
} // getParentNode()
//--------------------------------------------------------------------------------------------------
@Override
public void setParentNode(final ConditionParentNode parentNode) {
  _parentNode = parentNode;
} // setParentNode()
//--------------------------------------------------------------------------------------------------
@Override
public String toString() {
  String result = "";
  if (_parentNode != null && _parentNode.getChildNodeList().size() > 0 &&
      this != _parentNode.getChildNodeList().get(0)) {
    result += _conjunction.getToStringValue() + " ";
  }
  String operand;
  if (_operandsString == null) {
    operand = _operandsLong == null ? "" : String.valueOf(_operandsLong[0]);
  }
  else {
    operand = "'" + _operandsString[0] + "'";
  }
  result += (_operator == EOperator.False ? "!" : "") +
            _keyType.name() +
            (_operator.getIsBoolean() ? "" : " " + _operator.getOperatorString() +
                                             (operand.isEmpty() ? "" : " " + operand));
  return result;
} // toString()
//--------------------------------------------------------------------------------------------------
}